/**
 * Copyright 2004 Cordys R&D B.V. 
 * 
 * This file is part of the Cordys FTP Connector. 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 package com.eibus.applicationconnector.ftp;

import java.io.File;

import com.eibus.xml.nom.Node;
import com.eibus.xml.xpath.XPath;
import com.eibus.soap.fault.Fault;
import com.eibus.xml.nom.Document;
import com.eibus.util.logger.Severity;
import com.eibus.util.logger.CordysLogger;
import com.eibus.applicationconnector.ftp.localization.message.Messages;
import com.eibus.directory.soap.DirectoryException;
import com.eibus.exception.ExceptionGroup;

/**
 * This class is the handler for FTP's APPEND command
 */
class Append extends FTPCommand
{
	boolean sync = false;
    private int publishNode;
    private int responseFilesNode;
	private String basepath = "";
	private static Document document;
    private static final String STR_FILES ="files";
    private static final CordysLogger logger = CordysLogger.getCordysLogger(Append.class);

	@Override
	public boolean prepare()
    {
		logger.log(Severity.DEBUG, "Request received to APPEND's prepare() method is "  + Node.writeToString(requestXML, true));
		document = Node.getDocument(requestXML);
		responseFilesNode = Node.duplicate(Node.getElementNS(requestXML, STR_FILES, getNameSpace()));
		
        if( responseFilesNode == 0 )
        {
        	logger.log(Severity.ERROR, "There is no <files> node available in the request. Throwing a SOAP:Fault.");
        	Fault fault = response.createSOAPFault(Fault.Codes.CLIENT,Messages.FILES_NOT_SPECIFIED_APPEND);
        	addFaultDetails2(fault.getDetail(),null,DEFAULT_QNAME,null,null);
        	return false;
        }
        
        int[] fileNodes = XPath.getMatchingNodes("path", null, responseFilesNode);
        if( fileNodes.length == 0 )
        {
        	logger.log(Severity.ERROR, "There are no <file> nodes available in the request. Throwing a SOAP:Fault.");
        	Fault fault = response.createSOAPFault(Fault.Codes.CLIENT,Messages.FILES_NOT_SPECIFIED_APPEND);
        	addFaultDetails2(fault.getDetail(),null,DEFAULT_QNAME,null,null);
        	return false;
        }

        boolean soapFault = false;
        /*
         * Under the <files> node, there can be any number of <file> nodes be present
         * Call the append command for each file node
         */
        for (int index = 0; index < fileNodes.length; index ++)
        {
        	int sourceFileNode = XPath.getFirstMatch(".//source", null, fileNodes[index]);
        	String sourceFile = Node.getDataWithDefault(sourceFileNode, "").trim();

        	/* Convert the given file separator character to '/' */
            sourceFile = getProperFileSeparator(sourceFile, true);
            File file = null;
            
            /* Check if the request is from C3 or BOP-4. C3 doesn't contain basepath */
            if (ftpConfiguration.getBasepath() == null)
    		{
            	logger.log(Severity.DEBUG, "There is no basepath available in the configuration. Request is received from C3");
            	file = new File("/" + sourceFile);
    		}
            else
            {
            	logger.log(Severity.DEBUG, "There is basepath available in the configuration. Request is received from BOP-4");
            	basepath = ftpConfiguration.getBasepath();
                /*
                 * Source file path should not contain ".", ".." or ":"
                 */
                if (!validate(sourceFile))
                {
                	response.createSOAPFault(Fault.Codes.CLIENT, Messages.INVALID_SOURCE);
                    return false;
                }
                
                /* Append the sourceFile to the basepath to get the absolute path */
                file = new File(basepath + "/" + sourceFile);
            }
            if (!file.isFile())
            {
            	soapFault = true;
            	document.createTextElement("status", "The system cannot find the file specified or the access is denied.", fileNodes[index]);
            	logger.log(Severity.ERROR, file.getAbsolutePath() + " doesn't exists. Throwing a SOAP:Fault");
            	continue;
            }
    		
            String statusMessage = inProgressMesg;
            
            if(sourceFile.length() == 0)
            {
            	logger.log(Severity.ERROR, "The source file for append command is not specified.");
        		Fault fault = response.createSOAPFault(Fault.Codes.CLIENT,Messages.SOURCE_NOT_SPECIFIED);
        		addFaultDetails2(fault.getDetail(),null,DEFAULT_QNAME,null,null);
        		return false;
            }

            /* Add the statuscode only if the protocol is FTP. Because, SFTP client doesn't return any status codes */ 
    		if (!ftpConfiguration.getProtocol().equals("SFTP"))
    		{
    			document.createTextElement("statuscode", "", fileNodes[index]);
    		}
    		
            document.createTextElement("status", statusMessage , fileNodes[index]);
        }
        
        if (soapFault)
        {
        	for (int index = 0; index < fileNodes.length; index ++)
            {
        		int statusMessageNode = XPath.getFirstMatch("status", null, fileNodes[index]);
            	if (Node.getDataWithDefault(statusMessageNode, "").equals(inProgressMesg))
            	{
            		Node.setDataElement(Node.getParent(statusMessageNode), "status", "");
            	}
            }
        		
        	Fault fault = response.createSOAPFault(Fault.Codes.CLIENT, Messages.SPECIFIED_FILE_NOT_FOUND);
        	fault.getDetail().addDetailEntry(responseFilesNode);
            return false;                	
        }
        
		/* Create publish node for publishing if it is invoked in asynchronous mode */
		publishNode = document.createElementNS("Append", null, null, getNameSpace(), 0);
		addRequestDetailsToResponse(requestXML, publishNode);
		
		int pFilesNode = document.createTextElement(STR_FILES, null, publishNode);
		Node.appendToChildren(pFilesNode,publishNode);
        return true;
    }

	/**
	 * This method creates a new thread and delegates the append operation to it
	 */
	@Override
	public void commit()
	{
		try
		{
	        Thread appendThread = new Thread(new Appender(responseFilesNode, Node.duplicate(publishNode)));
	        appendThread.start();
	
	        int responseXML = response.getXMLNode();
	        sync = Node.getDataWithDefault(Node.getElementNS(requestXML,"synchronous",getNameSpace()),"false").equals("true");
	        
	        /* If synchronous is set to true, then wait till the thread finishes the append operation */
	        if(sync)
	        {
	        	logger.log(Severity.DEBUG, "<synchronous> is set to true. Waiting for the thread to finish its execution.");
	        	try
	        	{
	        		appendThread.join();
				}
	        	catch (InterruptedException e)
				{
					logger.log(Severity.ERROR, "An exception has occured while downloading " + e.getMessage(), e);
				}
	        }
	        Node.appendToChildren(Node.duplicate(responseFilesNode),responseXML);
		} finally
		{
			Node.delete(publishNode);
			publishNode = 0;
			
			if (sync)
			{
				Node.delete(responseFilesNode);
				responseFilesNode = 0;
			}
		}
	}

	/*
	 * This is the actual worker thread. Implements Runnable
	 * and does the requested operation in the run() method
	 */
    private class Appender implements Runnable
    {
        private int appenderFilesNode;
        private int appenderPublishNode;
        
        private String type;
        private String ftpMessage;
        
        
        Appender(int filesNode, int publishNode)
        {
        	this.appenderFilesNode = filesNode;
        	this.appenderPublishNode = publishNode;
        }
        
        @Override
		public void run()
		{
        	try
        	{
	        	int[] fileNodes = XPath.getMatchingNodes("path", null, appenderFilesNode);
	        	for (int index = 0; index < fileNodes.length; index ++)
	        	{
	        		int statuscode = Node.getElement(fileNodes[index], "statuscode");
	        		int statusNode = Node.getElement(fileNodes[index], "status");
	            	this.type = Node.getAttribute(fileNodes[index], "type", "ascii");
	
	            	/* Set the appropriate transfer type: ASCII or BINARY */
	                if(this.type.equals("ascii"))
	                {
	                	adapter.setFileType(IFTPClient.FILE_TYPE.ASCII);
	    			}
	
	                if(this.type.equals("bin"))
	                {
	                	adapter.setFileType(IFTPClient.FILE_TYPE.BINARY);
	    			}
	
	            	String source = Node.getDataWithDefault(XPath.getFirstMatch("source", null, fileNodes[index]), "");
	            	String target = Node.getDataWithDefault(XPath.getFirstMatch("target", null, fileNodes[index]), "");
	            	
	            	source = getProperFileSeparator(source, false);
	            	target = getProperFileSeparator(target, true);
	            	
	            	/* If the request is from BOP-4, get the absolute path by appending source to the basepath */
	            	if (ftpConfiguration.getBasepath() != null)
	            	{
	            		source = basepath + "/" + source;	//NOPMD	
	            	}
	            	
					try
					{
						logger.log(Severity.DEBUG, "Invoking the append method with source " + source + " and target " + target);
						
						/* Invoke the actual append method of the appropriate adapter */
						ftpMessage = adapter.append(source, target);
					}
					catch (FTPException e)
					{
						adapter.disconnect();
						logger.log(Severity.ERROR, "APPEND operation failed. " + e.getMessage());
			            Fault fault = response.createSOAPFault(Fault.Codes.CLIENT, Messages.APPEND_FAILURE, new String[]{e.getLocalizedMessage()});
			            addFaultDetails2(fault.getDetail(),e,DEFAULT_QNAME,null,null);
			            return;
					}
	
	            	/*
	            	 * ftpMessage contains both status code and status message.
	            	 * Split them and assign to respective nodes.
	            	 */
					if (ftpConfiguration.getProtocol().equals("SFTP"))
					{
		                Node.setDataElement(Node.getParent(statusNode), "status", ftpMessage);
		                Node.appendToChildren(statusNode, fileNodes[index]);
					}
					else
					{
						Node.setDataElement(Node.getParent(statuscode), "statuscode", ftpMessage.substring(0, 3));
		                Node.setDataElement(Node.getParent(statusNode), "status", ftpMessage.substring(4));
		                Node.appendToChildren(statuscode, fileNodes[index]);
		                Node.appendToChildren(statusNode, fileNodes[index]);
					}
					
	    			int pfilesNode = Node.getElementNS(appenderPublishNode, STR_FILES, getNameSpace());
	    			Node.appendToChildren(Node.duplicate(fileNodes[index]), pfilesNode);
	    			
	    			/* If the <synchronous> is set to false, then publish the status to the Event Service */
	    			try
	    			{
						publishFTPMessage(ftpNotificationSubject, Node.duplicate(this.appenderPublishNode));
					}
	    			catch (ExceptionGroup e)
	    			{
	    				logger.log(Severity.ERROR, "An exception has occurred while publishing to Event Service " + e.getMessage(), e);
	    			}
	    			catch (DirectoryException e)
	    			{
	    				logger.log(Severity.ERROR, "An exception has occurred while publishing to Event Service " + e.getMessage(), e);
					}
	        	}
	        	
	        	/* After the command is complete, disconnect the adapter from the FTP server */
	            adapter.disconnect();
        	} finally
        	{
        		Node.delete(appenderPublishNode);
        		appenderPublishNode = 0;
        		
        		if(!sync)
        		{
        			Node.delete(appenderFilesNode);
        			appenderFilesNode = 0;
        		}
        	}
		}
    }
}
